/*
 ********************************************************************************
 *
 * Copyright (c) 2019-2024 Renesas Electronics Corporation and/or its affiliates
 *
 ********************************************************************************
 */

package com.dialog.suotalib.activities;

import android.bluetooth.BluetoothDevice;
import android.os.Bundle;
import android.text.Editable;
import android.text.method.ScrollingMovementMethod;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.greyshark.lib.bluetooth.R;
import com.dialog.suotalib.dialogs.DialogFragmentBuilder;
import com.dialog.suotalib.dialogs.DialogFragmentManager;
import com.dialog.suotalib.dialogs.SupportCustomDialogFragment;
import com.dialog.suotalib.global.SuotaLibConfig;
import com.dialog.suotalib.global.SuotaProfile;
import com.dialog.suotalib.interfaces.IBaseSuota;
import com.dialog.suotalib.interfaces.callbacks.ISuotaManagerCallback;
import com.dialog.suotalib.suota.SuotaFile;
import com.dialog.suotalib.suota.SuotaManager;

import java.util.Locale;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;

/**
 * This abstract activity class provides the core utilities of running suota.
 * You can customize the UI by overriding the appropriate methods
 * and implementing the abstract methods.
 *
 * Use the view ids mentioned bellow to let this class handle interaction with them.
 * If a view is missing the library simply ignores it.
 *
 * A full implemented subclass of this is the {@link RunAutoSuotaActivity}.
 *
 * @see RunAutoSuotaActivity
 */
public abstract class BaseSuotaActivity extends AppCompatActivity implements IBaseSuota {

    private static final String TAG = "RunAutoSuotaActivity";

    /**
     * Enum used to specify the desired info that is going to be shown on {@link #currentlySendingTv}
     */
    public enum CurrentlySendingMode {
        /**
         * Only the currently block count out of total firmware blocks
         */
        ONLY_BLOCK,
        /**
         * Only the currently chunk count out of total firmware chunks
         */
        ONLY_CHUNK_OF_TOTAL,
        /**
         * Combination of above, adding also info about the currently chunk count
         * out of the currently block chunks
         */
        BOTH_CHUNK_AND_BLOCK
    }

    // region Views

    /**
     * The view bellow is being shown while suota protocol start running
     * but before the {@link ISuotaManagerCallback#onDeviceReady()} callback gets triggered.
     * For instance, this could be a linear layout with progress bar and a text view.
     *
     * <i>id: initialization</i>
     */
    private View initializationLy;

    /**
     * The view bellow is being shown when the {@link ISuotaManagerCallback#onDeviceReady()} callback gets triggered
     * For instance, this could be a scrollview wrapping all other views.
     *
     * <i>id: wrapper</i>
     */
    private View wrapperLy;

    /**
     * Indicates the progress of the firmware update.
     *
     * The progress bar updates its state whenever the {@link ISuotaManagerCallback#onUploadProgress(float)}
     * callback gets triggered.
     *
     * <i>id: progress_bar</i>
     */
    private ProgressBar progressBar;

    /**
     * Indicates the progress of the firmware update.
     * Displays the value of the associated progress bar.
     *
     * Updates its state whenever the {@link ISuotaManagerCallback#onUploadProgress(float)}
     * callback gets triggered.
     *
     * <i>id: progress_bar_text</i>
     */
    private TextView progressTv;

    /**
     * Displays what is currently sending.
     * Depending on the {@link #getSendingModeDisplay()} implementation,
     * it can either display only the current block of total blocks of the firmware update,
     * or the current chunk of total chunks of the firmware update, or both including the
     * current chunk count of the current block chunks.
     *
     * Updates its value whenever the {@link ISuotaManagerCallback#onChunkSend(int, int, int, int, int, int)}
     * callback gets triggered.
     *
     * <i>id: progress_detail</i>
     */
    private TextView currentlySendingTv;

    private TextView blockSpeedTv;
    private TextView maxBlockSpeedTv;
    private TextView minBlockSpeedTv;
    private TextView avgSpeedTv;

    /**
     * Displays more details about the state of the suota protocol that is running.
     * Also at the end displays the elapsed time and if a reboot has been sent or not.
     *
     * Updates its value whenever the {@link ISuotaManagerCallback#onSuotaLog(SuotaProfile.SuotaProtocolState, ISuotaManagerCallback.SuotaLogType, String)}
     * callback gets triggered.
     *
     * <i>id: logcat</i>
     */
    private TextView logcat;

    /**
     * Close Button
     */
    private Button closeButton;

    // endregion

    private boolean suotaFinished;
    private SupportCustomDialogFragment customDialogFragment;

    // region Abstract Methods

    /**
     * Getter for the resource layout id.
     *
     * @return layout id
     */
    protected abstract int getLayout();

    /**
     * Getter for the SuotaManager object.
     *
     * @return the SuotaManager handling the update
     * @see SuotaManager
     */
    protected abstract SuotaManager getSuotaManager();

    /**
     * Getter for the SuotaFile/firmware that is going to be used for the Suota.
     *
     * @return the SuotaFile itself
     * @see SuotaFile
     */
    protected abstract SuotaFile getSuotaFile();

    /**
     * Getter for the BluetoothDevice with which we are going to connect to run suota.
     *
     * @return the BluetoothDevice itself
     */
    protected abstract BluetoothDevice getBluetoothDevice();

    /**
     * Getter for the Currently Sending Mode, that indicates which info is going to be shown to the
     * {@link #currentlySendingTv Currently Sending TextView}.
     *
     * @return the selected CurrentlySendingMode
     * @see CurrentlySendingMode
     */
    protected abstract @NonNull CurrentlySendingMode getSendingModeDisplay();

    // endregion

    // region View Getters

    /**
     * Getter for the ProgressBar View.
     *
     * @return the progressBar. Returns null when no view with the associated
     * id is available
     */
    public @Nullable ProgressBar getProgressBar() {
        return progressBar;
    }

    /**
     * Getter for the Progress TextView.
     *
     * @return the TextView displaying the progress. Returns null when no view
     * with the associated id is available
     */
    public @Nullable TextView getProgressTv() {
        return progressTv;
    }

    /**
     * Getter for the Currently Sending TextView.
     *
     * @return the TextView displaying the currently sending info. Returns null
     * when no view with the associated id is available
     */
    public @Nullable TextView getCurrentlySendingTv() {
        return currentlySendingTv;
    }

    /**
     * Getter for the Logcat TextView.
     *
     * @return the TextView displaying the suota protocol state info. Returns null
     * when no view with the associated id is available
     */
    public @Nullable TextView getLogcat() {
        return logcat;
    }

    /**
     * Getter for the close button.
     *
     * @return the closeButton itself
     */
    public @Nullable Button getCloseButton() {
        return closeButton;
    }

    // endregion

    // region Override AppCompatActivity Methods

    /**
     * Enables the Up Navigation and displays the appropriate icon to the Toolbar.
     * Binds all available views and makes the appropriate initializations on the views.
     *
     * @param savedInstanceState the saveInstanceState
     */
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayout());

        if (getSupportActionBar() != null)
            getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        initializationLy = findViewById(R.id.suota_initialization);
        wrapperLy = findViewById(R.id.suota_wrapper);
        logcat = findViewById(R.id.suota_logcat);
        progressBar = findViewById(R.id.suota_progress_bar);
        progressTv = findViewById(R.id.suota_progress_bar_text);
        currentlySendingTv = findViewById(R.id.suota_progress_detail);
        blockSpeedTv = findViewById(R.id.suota_block_speed_value);
        maxBlockSpeedTv = findViewById(R.id.suota_max_block_speed_value);
        minBlockSpeedTv = findViewById(R.id.suota_min_block_speed_value);
        avgSpeedTv = findViewById(R.id.suota_avg_speed_value);
        closeButton = findViewById(R.id.suota_close_button);

        if (closeButton != null)
            closeButton.setVisibility(View.GONE);
        if (initializationLy != null) {
            initializationLy.setVisibility(View.VISIBLE);
            if (wrapperLy != null)
                wrapperLy.setVisibility(View.GONE);
        }
        if (logcat != null) {
            logcat.setText(null, TextView.BufferType.EDITABLE);
            logcat.setMovementMethod(new ScrollingMovementMethod());
        }
        if (progressBar != null) {
            progressBar.setVisibility(View.VISIBLE);
            progressBar.setProgress(0);
        }
    }

    /**
     * Handles on back press when suota is still running and finishes the activity.
     */
    @Override
    public void onBackPressed() {
        finish();
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    /**
     * Handles the destruction of the SuotaManager object, as well as the disconnection from the device.
     * Also dismisses any visible {@link com.dialog.suotalib.dialogs.CustomDialogFragment}.
     *
     * @see SuotaManager
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (customDialogFragment != null)
            customDialogFragment.dismiss();
        getSuotaManager().disconnect();
    }

    // endregion

    /**
     * Start the update by calling the {@link SuotaManager#startUpdate()} and
     * handles the visibility between the {@link #initializationLy} and the {@link #wrapperLy} views.
     *
     * @see SuotaManager
     */
    @Override
    public void startSuota() {
        if (initializationLy != null)
            initializationLy.setVisibility(View.GONE);
        if (wrapperLy != null)
            wrapperLy.setVisibility(View.VISIBLE);
        getSuotaManager().startUpdate();
    }

    /**
     * Appends the string passed as param to the {@link #logcat} TextView if it is available.
     *
     * @param log the string wanted to be append to the TextView
     */
    @Override
    public void appendToLogcat(String log) {
        if (logcat != null) {
            Editable text = logcat.getEditableText();
            if (text.length() != 0)
                text.append("\n");
            text.append(log);
        }
    }

    /**
     * In case the {@link SuotaLibConfig#AUTO_REBOOT} is set to <i>false</i>
     * this method triggers the {@link #displayCloseButton()}.
     *
     * @param totalElapsedSecs       the total elapsed millis during Suota protocol execution
     * @param imageUploadElapsedSecs the elapse millis during sending the actual image/firmware
     */
    @Override
    public void onSuccess(double totalElapsedSecs, double imageUploadElapsedSecs) {
        suotaFinished = true;
        if (!SuotaLibConfig.AUTO_REBOOT)
            displayCloseButton();
    }

    /**
     * Updates the text that the {@link #currentlySendingTv} is showing. Depending on the return value of
     * {@link #getSendingModeDisplay()} different info is being used to populate the TextView.
     *
     * @param chunkCount chunk count of total chunks
     * @param totalChunks total chunks
     * @param chunk chunk count of current block
     * @param block block count
     * @param blockChunks total chunks in current block
     * @param totalBlocks total blocks
     */
    @Override
    public void updateCurrentlySendingInfo(int chunkCount, int totalChunks, int chunk, int block, int blockChunks, int totalBlocks) {
        if (currentlySendingTv == null)
            return;

        if (getSendingModeDisplay() == CurrentlySendingMode.ONLY_BLOCK) {
            currentlySendingTv.setText(getString(R.string.suota_sending_block, block, totalBlocks));
        } else if (getSendingModeDisplay() == CurrentlySendingMode.ONLY_CHUNK_OF_TOTAL) {
            currentlySendingTv.setText(getString(R.string.suota_sending_chunk_of_totals, chunkCount, totalChunks));
        } else {
            currentlySendingTv.setText(getString(R.string.suota_sending_both, chunkCount, totalChunks, block, totalBlocks, chunk));
        }
    }

    /**
     * Updates the {@link #progressBar Progress Bar} and the {@link #progressTv Progress TextView},
     * using the param value.
     *
     * @param percent percent of the upload done
     */
    @Override
    public void updateProgress(float percent) {
        updateProgressBar((int) percent);
        updateProgressTv(String.valueOf((int) percent));
    }

    /**
     * Displays a toast informing about the completion with reboot
     * and if the {@link SuotaLibConfig#AUTO_REBOOT} is set to <i>true</i>
     * then this method triggers the {@link #displayCloseButton()}.
     */
    @Override
    public void rebootSent() {
        appendToLogcat(getString(R.string.suota_reboot_sent));
        if (closeButton != null)
            displayCloseButton();
    }

    /**
     * Displays a {@link com.dialog.suotalib.dialogs.CustomDialogFragment} informing the user about
     * the error occurred, using the {@link SuotaProfile.Errors#suotaErrorCodeList} and the
     * passed parameter as an index.
     *
     * @param errorSuotaLibCode the error code using the suota lib error code list
     * @see SuotaProfile.Errors
     */
    @Override
    public void displayErrorDialog(int errorSuotaLibCode) {
        displayErrorDialog(SuotaProfile.Errors.suotaErrorCodeList.get(errorSuotaLibCode));
    }

    /**
     * Displays a {@link com.dialog.suotalib.dialogs.CustomDialogFragment} informing the user about
     * the error occurred, using the string param.
     *
     * @param errorMsg the message to display to the dialog
     */
    @Override
    public void displayErrorDialog(String errorMsg) {
        if (customDialogFragment != null) {
            customDialogFragment.dismiss();
        }

        customDialogFragment = new DialogFragmentBuilder()
                .setType(DialogFragmentBuilder.ERROR)
                .setTitle(getString(R.string.suota_error_title))
                .setMessage(errorMsg)
                .setIconResourceId(R.drawable.suota_ic_error_outline_black_36dp)
                .buildV4();

        DialogFragmentManager.OnResponseListener onDisplayErrorResponse = new DialogFragmentManager.OnResponseListener() {
            @Override
            public void onActionPerformed() {
                Toast.makeText(getApplicationContext(), R.string.suota_disconnected, Toast.LENGTH_SHORT).show();
                finish();
            }
        };

        customDialogFragment.setOnPositiveClickListener(onDisplayErrorResponse);
        customDialogFragment.setOnDismissListener(onDisplayErrorResponse);
        customDialogFragment.setOnDialogBackPressedListener(onDisplayErrorResponse);
        customDialogFragment.showDialog(getSupportFragmentManager(), getLifecycle());
    }

    /**
     * Handles disconnection by displaying an appropriate Toast, and finishing the current activity
     * if the suota was still running.
     */
    @Override
    public void deviceDisconnected() {
        Toast.makeText(getApplicationContext(), R.string.suota_disconnected, Toast.LENGTH_SHORT).show();
        if (!suotaFinished && customDialogFragment == null)
            finish();
    }

    /**
     * Handles UI updates after suota has successfully finished.
     */
    @Override
    public void onSuotaFinished() {
        if (customDialogFragment != null)
            customDialogFragment.dismiss();
    }

    @Override
    public void updateSpeedStatistics(double current, double max, double min, double avg) {
        if (blockSpeedTv != null)
            blockSpeedTv.setText(String.format(Locale.US, "%d B/s", (int) current));
        if (maxBlockSpeedTv != null)
            maxBlockSpeedTv.setText(String.format(Locale.US, "%d B/s", (int) max));
        if (minBlockSpeedTv != null)
            minBlockSpeedTv.setText(String.format(Locale.US, "%d B/s", (int) min));
        if (avgSpeedTv != null)
            avgSpeedTv.setText(String.format(Locale.US, "%d B/s", (int) avg));
    }

    /**
     * This method gets triggered when the
     * suota is finished, in order to create and add a closeButton to the screen.
     */
    public void displayCloseButton() {
        if (closeButton != null) {
            closeButton.setOnClickListener(getButtonClickListener());
            closeButton.setVisibility(View.VISIBLE);
        }
    }

    /**
     * Getter for the desired OnClickListener, that is going to be set to the {@link #closeButton}.
     * By default it finishes the current activity.
     *
     * @return the OnClickListener itself
     */
    protected View.OnClickListener getButtonClickListener() {
        return new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                finish();
            }
        };
    }

    /**
     * Getter for the parent ViewGroup that the {@link #closeButton} is going to be added to.
     * By default the ViewGroup is found using the <i>suota_root</i> id.
     *
     * @return the ViewGroup itself
     */
    protected ViewGroup getParentViewGroup() {
        return findViewById(R.id.suota_root);
    }

    private void updateProgressBar(int percent) {
        if (progressBar != null)
            progressBar.setProgress(percent);
    }

    private void updateProgressTv(String percent) {
        if (progressTv != null)
            progressTv.setText(percent);
    }
}
